﻿using System;
using System.Collections.Generic;
using ICSharpCode.NRefactory.Ast;
using ICSharpCode.NRefactory.Visitors;

namespace NTrace
{
    internal class NTraceAstVisitor : AbstractAstVisitor
    {
        private const String MessageGuidVariableName = "_etwMessageGuid";
        private const String GetHashCodeMethodName = "GetHashCode";
        private PreprocessorContext context;

        public NTraceAstVisitor(PreprocessorContext context)
        {
            this.context = context;
        }

        public override object VisitTypeDeclaration(TypeDeclaration typeDeclaration, object data)
        {
            // TODO: We're being lazy here and always adding this member. It would be
            // better if we scanned the type first to see if we really needed to add it.
            if (typeDeclaration.Type == ClassType.Class || typeDeclaration.Type == ClassType.Struct)
            {
                this.AddMessageGuidMember(typeDeclaration);
            }
            return base.VisitTypeDeclaration(typeDeclaration, data);
        }

        public override object VisitFieldDeclaration(FieldDeclaration fieldDeclaration, object data)
        {
            foreach (VariableDeclaration variable in fieldDeclaration.Fields)
            {
                String fullyQualifiedPath = NTraceAstVisitor.GetFullyQualifiedPath(variable, fieldDeclaration.Parent as TypeDeclaration);
                if (this.IsProviderDeclaration(fullyQualifiedPath))
                {
                    this.InjectProviderCreationStatement(fieldDeclaration, variable);
                }
            }

            return base.VisitFieldDeclaration(fieldDeclaration, data);
        }

        private bool IsProviderDeclaration(String fullyQualifiedPath)
        {
            return this.context.Environment.EtwProviderVariable.CompareTo(fullyQualifiedPath) == 0;
        }

        private void InjectProviderCreationStatement(FieldDeclaration fieldDeclaration, VariableDeclaration variable)
        {
            String providerIdString = this.context.Environment.ProviderId.ToString("D");
            ObjectCreateExpression providerIdExpression = new ObjectCreateExpression(
              new TypeReference("System.Guid"),
              new List<Expression>(new Expression[] { new PrimitiveExpression(providerIdString, providerIdString) }));
            variable.Initializer = new ObjectCreateExpression(
              fieldDeclaration.TypeReference,
              new List<Expression>(new Expression[] { providerIdExpression }));

            ICSharpCode.NRefactory.PrettyPrinter.IOutputAstVisitor printer = this.context.Language.CreatePrinter();
            fieldDeclaration.AcceptVisitor(printer, null);
            String replacement = CodeUtilities.RemoveLineSeparators(printer.Text);
            this.context.Lines[fieldDeclaration.StartLocation.Line - 1] = replacement;
        }

        public override object VisitExpressionStatement(ExpressionStatement expressionStatement, object data)
        {
            var output = base.VisitExpressionStatement(expressionStatement, data);
            InvocationExpression invocationExpression = expressionStatement.Expression as InvocationExpression;
            if (invocationExpression != null)
            {
                // First, check to see if it's a reference to a member named "Trace" (or whatever the configured environment is using)...
                MemberReferenceExpression traceCall = invocationExpression.TargetObject as MemberReferenceExpression;
                if (traceCall != null && String.Compare(traceCall.MemberName, this.context.Environment.Configuration.Macros.Trace, StringComparison.InvariantCulture) == 0)
                {
                    // Next, check to see if it's a member on the trace class
                    IdentifierExpression virtualClass = traceCall.TargetObject as IdentifierExpression;
                    if (
                      virtualClass != null &&
                      String.Compare(virtualClass.Identifier, this.context.Environment.TraceClassName, StringComparison.InvariantCulture) == 0)
                    {
                        // Ladies and gentlemen, we have a call to our trace macro.

                        // Attempt to find the member that "owns" this call.
                        ParametrizedNode parent = null;
                        for (INode currentNode = expressionStatement.Parent;
                          parent == null && currentNode != null;
                          parent = currentNode as ParametrizedNode, currentNode = currentNode.Parent) ;

                        this.ProcessTraceCall(parent, context, expressionStatement, invocationExpression);
                    }
                }
            }
            return output;
        }

        public override object VisitMemberReferenceExpression(MemberReferenceExpression memberReferenceExpression, object data)
        {
            IdentifierExpression identifierExpression = memberReferenceExpression.TargetObject as IdentifierExpression;
            // Is it the trace pseudo class?
            if (identifierExpression != null && String.Compare(identifierExpression.Identifier, this.context.Environment.TraceClassName, StringComparison.InvariantCulture) == 0)
            {
                String replacementStatement = null;

                // Is it a check against the Enabled property?
                if (String.Compare(memberReferenceExpression.MemberName, this.context.Environment.Configuration.Macros.EnabledCheck, StringComparison.InvariantCulture) == 0)
                {
                    // It's a check against the Enabled property. Time to replace the target object.
                    memberReferenceExpression.TargetObject = this.context.Environment.GetTraceProviderReference();
                    ICSharpCode.NRefactory.PrettyPrinter.IOutputAstVisitor printer = context.Language.CreatePrinter();
                    memberReferenceExpression.AcceptVisitor(printer, null);

                    replacementStatement = printer.Text;
                }

                if (!String.IsNullOrEmpty(replacementStatement))
                {
                    NTraceAstVisitor.ReplaceRange(
                      context,
                      memberReferenceExpression,
                      replacementStatement,
                      memberReferenceExpression.StartLocation.Line - 1,
                      memberReferenceExpression.StartLocation.Column - 1,
                      memberReferenceExpression.EndLocation.Line - 1,
                      memberReferenceExpression.EndLocation.Column - 1,
                      false);
                }
            }
            return base.VisitMemberReferenceExpression(memberReferenceExpression, data);
        }

        public override object VisitInvocationExpression(InvocationExpression invocationExpression, object data)
        {
            Expression replacementExpression = null;

            MemberReferenceExpression memberReferenceExpression = invocationExpression.TargetObject as MemberReferenceExpression;
            if (memberReferenceExpression != null)
            {
                IdentifierExpression identifierExpression = memberReferenceExpression.TargetObject as IdentifierExpression;

                // Is it the trace pseudo class?
                if (identifierExpression != null && identifierExpression.Identifier.CompareTo(this.context.Environment.TraceClassName) == 0)
                {
                    if (invocationExpression.Arguments.Count == 1)
                    {
                        MemberReferenceExpression enumReferenceExpression = invocationExpression.Arguments[0] as MemberReferenceExpression;

                        if (enumReferenceExpression != null)
                        {
                            IdentifierExpression enumIdentifierExpression = enumReferenceExpression.TargetObject as IdentifierExpression;

                            // Is it a check against the Flags property?
                            if (String.Compare(memberReferenceExpression.MemberName, this.context.Environment.Configuration.Macros.TraceFlagCheck, StringComparison.InvariantCulture) == 0)
                            {
                                if (enumIdentifierExpression.Identifier == context.Environment.TraceFlagName)
                                {
                                    int flag = context.Environment.Configuration.TraceFlags[enumReferenceExpression.MemberName];

                                    replacementExpression = new BinaryOperatorExpression(
                                    this.context.Environment.GetTraceEnabledCheck(),
                                    BinaryOperatorType.LogicalAnd,
                                    this.context.Environment.GetTraceFlagLevelCheck(flag, 0));
                                }
                            }

                            // Is it a check against the Level property?
                            if (String.Compare(memberReferenceExpression.MemberName, this.context.Environment.Configuration.Macros.TraceLevelCheck, StringComparison.InvariantCulture) == 0)
                            {
                                if (enumIdentifierExpression.Identifier == context.Environment.TraceLevelName)
                                {
                                    int level = context.Environment.Configuration.TraceLevels[enumReferenceExpression.MemberName];

                                    replacementExpression = new BinaryOperatorExpression(
                                      this.context.Environment.GetTraceEnabledCheck(),
                                      BinaryOperatorType.LogicalAnd,
                                      this.context.Environment.GetTraceLevelCheck(level));
                                }
                            }
                        }
                    }
                    else if (invocationExpression.Arguments.Count == 2)
                    {
                        MemberReferenceExpression flagEnumReferenceExpression = invocationExpression.Arguments[0] as MemberReferenceExpression;
                        MemberReferenceExpression levelEnumReferenceExpression = invocationExpression.Arguments[1] as MemberReferenceExpression;

                        // Is it a check to see if the flag and level are enabled?
                        if (String.Compare(memberReferenceExpression.MemberName, this.context.Environment.Configuration.Macros.TraceFlagLevelCheck, StringComparison.InvariantCulture) == 0)
                        {
                            // Let's verify that the inputs are what we expect
                            IdentifierExpression flagEnumIdentifierExpression = flagEnumReferenceExpression.TargetObject as IdentifierExpression;
                            IdentifierExpression levelEnumIdentifierExpression = levelEnumReferenceExpression.TargetObject as IdentifierExpression;

                            if (flagEnumIdentifierExpression != null && levelEnumIdentifierExpression != null &&
                              flagEnumIdentifierExpression.Identifier == context.Environment.TraceFlagName &&
                              levelEnumIdentifierExpression.Identifier == context.Environment.TraceLevelName)
                            {
                                int flag = context.Environment.Configuration.TraceFlags[flagEnumReferenceExpression.MemberName];
                                int level = context.Environment.Configuration.TraceLevels[levelEnumReferenceExpression.MemberName];

                                replacementExpression = new BinaryOperatorExpression(
                                  this.context.Environment.GetTraceEnabledCheck(),
                                  BinaryOperatorType.LogicalAnd,
                                  this.context.Environment.GetTraceFlagLevelCheck(flag, level));
                            }
                            else
                            {
                                // Error condition
                            }
                        }
                    }
                }
            }

            if (replacementExpression != null)
            {
                ICSharpCode.NRefactory.PrettyPrinter.IOutputAstVisitor printer = context.Language.CreatePrinter();
                replacementExpression.AcceptVisitor(printer, null);
                String replacementStatement = printer.Text;

                NTraceAstVisitor.ReplaceRange(
                  context,
                  memberReferenceExpression,
                  replacementStatement,
                  invocationExpression.StartLocation.Line - 1,
                  invocationExpression.StartLocation.Column - 1,
                  invocationExpression.EndLocation.Line - 1,
                  invocationExpression.EndLocation.Column - 1,
                  false);
            }

            return base.VisitInvocationExpression(invocationExpression, data);
        }

        private void AddMessageGuidMember(TypeDeclaration typeDeclaration)
        {
            FieldDeclaration messageGuidMember = new FieldDeclaration(null);
            messageGuidMember.Modifier = Modifiers.Static | Modifiers.Private;
            ObjectCreateExpression initializer = new ObjectCreateExpression(
                new TypeReference("System.Guid"),
                new List<Expression>(new Expression[] { new PrimitiveExpression(context.TraceMessageFile.MessageGuid.ToString("D"), context.TraceMessageFile.MessageGuid.ToString("D")) }));
            VariableDeclaration messageGuid = new VariableDeclaration(
              NTraceAstVisitor.MessageGuidVariableName,
              initializer,
              new TypeReference("System.Guid"));
            messageGuidMember.Fields.Add(messageGuid);

            ICSharpCode.NRefactory.PrettyPrinter.IOutputAstVisitor printer = context.Language.CreatePrinter();
            messageGuidMember.AcceptVisitor(printer, null);
            context.Lines[typeDeclaration.StartLocation.Line] += CodeUtilities.RemoveLineSeparators(printer.Text);
        }

        private static String GetFullyQualifiedPath(VariableDeclaration declaration, TypeDeclaration declaringType)
        {
            System.Text.StringBuilder buffer = new System.Text.StringBuilder();
            buffer.Append(declaration.Name);

            buffer.Insert(0, ".");
            buffer.Insert(0, declaringType.Name);

            INode parent = declaringType.Parent;
            while (parent != null)
            {
                TypeDeclaration typeParent = parent as TypeDeclaration;
                NamespaceDeclaration namespaceParent = parent as NamespaceDeclaration;
                if (typeParent != null)
                {
                    buffer.Insert(0, ".");
                    buffer.Insert(0, typeParent.Name);
                }
                else if (namespaceParent != null)
                {
                    buffer.Insert(0, ".");
                    buffer.Insert(0, namespaceParent.Name);
                }

                parent = parent.Parent;
            }

            return buffer.ToString();
        }

        private void ProcessTraceCall(ParametrizedNode parent, PreprocessorContext context, Statement statement, InvocationExpression invocationExpression)
        {
            List<Expression> traceArguments = new List<Expression>(invocationExpression.Arguments);
            if (traceArguments.Count < 1) throw new PreprocessorException(PreprocessorErrors.InvalidTraceArguments, "there should be at least one argument to the trace method.") { File = context.CodeFile, StartLine = statement.StartLocation.Line, StartColumn = statement.StartLocation.Column, EndLine = statement.EndLocation.Line, EndColumn = statement.EndLocation.Column };

            // invocationExpression can have three variants: one with just a flag, and another with a flag and level
            MemberReferenceExpression fieldReferenceExpression = traceArguments[0] as MemberReferenceExpression;

            byte level = 0;
            int flag;
            if (fieldReferenceExpression == null)
            {
                // TODO: Make these reference configuration values!
                level = 4; // Information
                flag = 16; // Diagnostics
            }
            else
            {
                String enumerationName = this.ExtractEnumerationName(traceArguments[0]);

                // Verify that the level enum in the code matches one of our configured enumerations
                if (this.context.Environment.TraceLevelName.CompareTo(enumerationName) == 0)
                {
                    System.Diagnostics.Debug.Assert(traceArguments[0] != null);
                    String levelString = this.ExtractEnumerationValue(traceArguments[0]);
                    if (!this.context.Environment.Configuration.TraceLevels.TryGetValue(levelString, out level))
                    {
                        throw new PreprocessorException(PreprocessorErrors.InvalidLevelValue, String.Format("invalid level value '{0}'.", levelString)) { File = context.CodeFile, StartLine = traceArguments[0].StartLocation.Line, StartColumn = traceArguments[0].StartLocation.Column, EndLine = traceArguments[0].EndLocation.Line, EndColumn = traceArguments[0].EndLocation.Column };
                    }

                    System.Diagnostics.Debug.Assert(traceArguments[1] != null);
                    enumerationName = this.ExtractEnumerationName(traceArguments[1]);
                    if (this.context.Environment.TraceFlagName.CompareTo(enumerationName) != 0)
                    {
                        throw new PreprocessorException(PreprocessorErrors.InvalidFlagEnumeration, String.Format("invalid flag enumeration '{0}'.", enumerationName)) { File = context.CodeFile, StartLine = traceArguments[1].StartLocation.Line, StartColumn = traceArguments[1].StartLocation.Column, EndLine = traceArguments[1].EndLocation.Line, EndColumn = traceArguments[1].EndLocation.Column };
                    }

                    System.Diagnostics.Debug.Assert(invocationExpression.Arguments[1] != null);
                    String flagString = this.ExtractEnumerationValue(invocationExpression.Arguments[1]);
                    if (!this.context.Environment.Configuration.TraceFlags.TryGetValue(flagString, out flag))
                    {
                        throw new PreprocessorException(PreprocessorErrors.InvalidFlagValue, String.Format("invalid flag value '{0}'.", flagString)) { File = context.CodeFile, StartLine = traceArguments[1].StartLocation.Line, StartColumn = traceArguments[1].StartLocation.Column, EndLine = traceArguments[1].EndLocation.Line, EndColumn = traceArguments[1].EndLocation.Column };
                    }

                    // Snip off the first two arguments so that the format string and the arguments remain
                    traceArguments.RemoveRange(0, 2);
                }
                else if (this.context.Environment.TraceFlagName.CompareTo(enumerationName) == 0)
                {
                    // Verify that the flag enum in the code matches one of our configured enumerations
                    System.Diagnostics.Debug.Assert(invocationExpression.Arguments != null && invocationExpression.Arguments[0] != null);
                    String flagString = this.ExtractEnumerationValue(invocationExpression.Arguments[0]);
                    if (!this.context.Environment.Configuration.TraceFlags.TryGetValue(flagString, out flag))
                    {
                        throw new PreprocessorException(PreprocessorErrors.InvalidFlagValue, String.Format("invalid flag value '{0}'.", flagString)) { File = context.CodeFile, StartLine = invocationExpression.Arguments[0].StartLocation.Line, StartColumn = invocationExpression.Arguments[0].StartLocation.Column, EndLine = invocationExpression.Arguments[0].EndLocation.Line, EndColumn = invocationExpression.Arguments[0].EndLocation.Column };
                    }

                    // Snip off the first argument so that the format string and the arguments remain
                    traceArguments.RemoveAt(0);
                }
                else
                {
                    throw new PreprocessorException(PreprocessorErrors.InvalidEnumeration, String.Format("invalid enumeration '{0}'.", enumerationName)) { File = context.CodeFile, StartLine = traceArguments[0].StartLocation.Line, StartColumn = traceArguments[0].StartLocation.Column, EndLine = traceArguments[0].EndLocation.Line, EndColumn = traceArguments[0].EndLocation.Column };
                }
            }

            Statement traceCall = this.CreateTraceCall(parent, context, statement.StartLocation.Line, traceArguments, level, flag);

            // It's a call to the trace method, so we'll wrap it with a conditional block.
            // First, generate the block tht will be contained by the conditional...
            ICSharpCode.NRefactory.PrettyPrinter.IOutputAstVisitor printer = context.Language.CreatePrinter();
            traceCall.AcceptVisitor(printer, null);
            String traceBlock = printer.Text;

            // Now let's generate the conditional that skips the call if the
            // level/flag combo isn't met
            Expression check = this.context.Environment.GetTraceFlagLevelCheck(flag, level);
            printer = context.Language.CreatePrinter();
            check.AcceptVisitor(printer, null);
            String checkString = CodeUtilities.RemoveLineSeparators(printer.Text);

            // VBNETSUPPORT: This is an unfortunate hack; the AST model provided by NRefactory
            // doesn't allow us to either specify where the if block begins OR
            // where the block starts/ends.
            String replacementStatement = String.Format(
                "if ({0}) {{{1}}}",
                checkString,
                traceBlock);

            NTraceAstVisitor.ReplaceStatement(context, statement, replacementStatement);
        }

        private static void ReplaceStatement(PreprocessorContext context, Statement statement, String replacementStatement)
        {
            int firstStatementLine = statement.StartLocation.Line - 1;
            int firstStatementCharacter = statement.StartLocation.Column - 1;
            int lastStatementLine = statement.EndLocation.Line - 1;
            int lastStatementCharacter = statement.EndLocation.Column - 1;

            NTraceAstVisitor.ReplaceRange(
              context,
              statement,
              replacementStatement,
              firstStatementLine,
              firstStatementCharacter,
              lastStatementLine,
              lastStatementCharacter,
              true);
        }

        private static void ReplaceRange(
          PreprocessorContext context,
          AbstractNode node,
          String replacementStatement,
          int firstStatementLine,
          int firstStatementCharacter,
          int lastStatementLine,
          int lastStatementCharacter,
          bool makeItFit)
        {
            // Remove the statement; we'll replace it with our conditional trace call
            String toAppend = null;
            if (lastStatementLine == firstStatementLine)
            {
                toAppend = context.Lines[firstStatementLine].Substring(lastStatementCharacter);
            }
            context.Lines[firstStatementLine] = context.Lines[firstStatementLine].Substring(0, firstStatementCharacter);
            int lineIndex = firstStatementLine;
            lineIndex++;
            while (lineIndex < lastStatementLine)
            {
                context.Lines[lineIndex] = String.Empty;
                lineIndex++;
            }
            if (context.Lines[lastStatementLine].Length >= lastStatementCharacter)
            {
                context.Lines[lastStatementLine] = context.Lines[lastStatementLine].Substring(lastStatementCharacter);
            }

            // for each line in the replacement statement, append the contents to the current line
            using (System.IO.StringReader reader = new System.IO.StringReader(replacementStatement))
            {
                lineIndex = firstStatementLine;
                String line;
                for (
                  line = reader.ReadLine();
                  line != null && lineIndex < lastStatementLine;
                  line = reader.ReadLine(), lineIndex++)
                {
                    context.Lines[lineIndex] += line;
                }
                if (line != null)
                {
                    String remainder;
                    if (makeItFit)
                    {
                        remainder = CodeUtilities.RemoveLineSeparators(line + reader.ReadToEnd());
                    }
                    else
                    {
                        remainder = line;
                    }
                    context.Lines[lineIndex] += remainder;
                }
            }

            context.Lines[lastStatementLine] += toAppend;
        }

        public Statement CreateTraceCall(
          ParametrizedNode parent,
          PreprocessorContext context,
          int line,
          IList<Expression> args,
          byte level,
          int flag)
        {
            List<Expression> traceArguments = new List<Expression>(args);

            // Extract the format string, and format a TMF message for it
            PrimitiveExpression formatString = traceArguments[0] as PrimitiveExpression;

            String formatStringContent = null;
            if (formatString != null)
                formatStringContent = formatString.Value as String;

            System.Diagnostics.Debug.Assert(formatStringContent != null);
            if (String.IsNullOrEmpty(formatStringContent))
            {
                throw new NTrace.PreprocessorException(PreprocessorErrors.FormatStringMustBeAString, "You must supply a literal string for the format string argument.");
            }

            // Snip off the first argument so that only the arguments remain
            traceArguments.RemoveAt(0);

            IList<String> argumentNames = NTraceAstVisitor.ExtractTraceArgumentNames(context, traceArguments);

            String methodName = "<Unknown>";
            bool isStatic = true;

            if (parent != null)
            {
                methodName = NTraceAstVisitor.GetFullName(parent);
                isStatic = (parent.Modifier & Modifiers.Static) == Modifiers.Static;
            }

            int messageNumber = context.TraceMessageFile.AddMessage(
              line,
              formatStringContent,
              flag,
              level,
              argumentNames,
              methodName,
              isStatic);

            // Replace with a method that takes the message ID and any format strings

            Expression getHashCodeExpression = null;

            if (!isStatic)
            {
                // It's an instance method
                MemberReferenceExpression getHashCodeReference = new MemberReferenceExpression(
                  new ThisReferenceExpression(),
                  NTraceAstVisitor.GetHashCodeMethodName);
                getHashCodeExpression = new InvocationExpression(getHashCodeReference);
            }

            // The trace method signature is traceProvider.Trace(messageNumber, hashCode (zero for static), string params);
            List<Expression> arguments = new List<Expression>();
            TypeDeclaration declaringClass = (parent.Parent as TypeDeclaration);
            System.Diagnostics.Debug.Assert(declaringClass != null, "Expected a non-null type declaration for the containing class.");
            MemberReferenceExpression messageGuidReference = new MemberReferenceExpression(
#if USELINQ
                new TypeReferenceExpression(declaringClass.CreateReference()),
#else
                new TypeReferenceExpression(TypeDeclarationExtensions.CreateReference(declaringClass)),
#endif
                NTraceAstVisitor.MessageGuidVariableName);

            arguments.Add(messageGuidReference);
            arguments.Add(new PrimitiveExpression(messageNumber, messageNumber.ToString()));

            // If it's an instance method, add the GetHashCode() call.
            if (getHashCodeExpression != null)
            {
                arguments.Add(getHashCodeExpression);
            }

            for (int index = 0; index < traceArguments.Count; index++)
            {
                Expression traceArgument = traceArguments[index];
                PrimitiveExpression primitiveArg = traceArgument as PrimitiveExpression;
                if (primitiveArg != null && primitiveArg.Value is String)
                {
                    arguments.Add(traceArgument);
                }
                else
                {
                    MemberReferenceExpression toStringReference = new MemberReferenceExpression(
                      traceArgument,
                      "ToString");
                    InvocationExpression toStringCall = new InvocationExpression(toStringReference);
                    arguments.Add(toStringCall);
                }
            }

            MemberReferenceExpression methodCall = new MemberReferenceExpression(
              this.context.Environment.GetTraceProviderReference(),
              "Trace");
            InvocationExpression traceCall = new InvocationExpression(
              methodCall,
              arguments);
            return new ExpressionStatement(traceCall);
        }

        private static IList<String> ExtractTraceArgumentNames(PreprocessorContext context, List<Expression> traceArguments)
        {
            IList<String> output = new List<String>();
            foreach (Expression traceArgument in traceArguments)
            {
                ICSharpCode.NRefactory.PrettyPrinter.IOutputAstVisitor printer = context.Language.CreatePrinter();

                System.Diagnostics.Debug.Assert(printer != null, "Should have a non-null printer.");

                traceArgument.AcceptVisitor(printer, null);
                String name = printer.Text;
                System.Diagnostics.Debug.Assert(!String.IsNullOrEmpty(name), "Should have a non-null trace argument name.");

                output.Add(name);
            }

            return output;
        }

        private string ExtractEnumerationName(Expression argument)
        {
            MemberReferenceExpression fieldReferenceExpression = argument as MemberReferenceExpression;
            if (fieldReferenceExpression == null) throw new PreprocessorException(PreprocessorErrors.InvalidExpression, "Invalid expression.") { File = this.context.CodeFile, StartLine = argument.StartLocation.Line, StartColumn = argument.StartLocation.Column, EndLine = argument.EndLocation.Line, EndColumn = argument.EndLocation.Column };
            IdentifierExpression identifierExpression = fieldReferenceExpression.TargetObject as IdentifierExpression;
            if (identifierExpression == null) throw new PreprocessorException(PreprocessorErrors.InvalidExpression, "Invalid expression.") { File = this.context.CodeFile, StartLine = argument.StartLocation.Line, StartColumn = argument.StartLocation.Column, EndLine = argument.EndLocation.Line, EndColumn = argument.EndLocation.Column };
            return identifierExpression.Identifier;
        }

        private string ExtractEnumerationValue(Expression argument)
        {
            MemberReferenceExpression fieldReferenceExpression = argument as MemberReferenceExpression;
            if (fieldReferenceExpression == null) throw new PreprocessorException(PreprocessorErrors.InvalidExpression, "Invalid expression.") { File = this.context.CodeFile, StartLine = argument.StartLocation.Line, StartColumn = argument.StartLocation.Column, EndLine = argument.EndLocation.Line, EndColumn = argument.EndLocation.Column };
            return fieldReferenceExpression.MemberName;
        }

        internal static String GetFullName(ParametrizedNode member)
        {
            System.Diagnostics.Debug.Assert(member != null, "Expected a non-null method declaration.");
            if (member == null) throw new ArgumentNullException("methodDeclaration");
            System.Text.StringBuilder buffer = new System.Text.StringBuilder();

            TypeDeclaration typeDeclaration = member.Parent as TypeDeclaration;
            System.Diagnostics.Debug.Assert(typeDeclaration != null, "Expected a non-null type declaration.");

            buffer.AppendFormat(
              "{0}.{1}",
              typeDeclaration.Name,
              member.Name);

            for (INode parent = typeDeclaration.Parent; parent != null && !(parent is CompilationUnit); parent = parent.Parent)
            {
                TypeDeclaration typeParent = parent as TypeDeclaration;
                NamespaceDeclaration namespaceParent = parent as NamespaceDeclaration;

                if (typeParent != null)
                {
                    buffer.Insert(0, ".");
                    buffer.Insert(0, typeParent.Name);
                }
                if (namespaceParent != null)
                {
                    buffer.Insert(0, ".");
                    buffer.Insert(0, namespaceParent.Name);
                }
            }

            return buffer.ToString();
        }
    }
}
